In [1]:
import pandas as pd
import numpy as np
import seaborn as sns
import plotly.express as px
import matplotlib.pyplot as plt
from matplotlib import pyplot as plt
%matplotlib inline
In [2]:
try:
    data = pd.read_csv('/datasets/data.csv')
except:
    data = pd.read_csv('https://code.s3.yandex.net/datasets/data.csv')

Задание 2. Выведите первые 20 строчек датафрейма data на экран.

In [3]:
data.head(20)
Out[3]:
children days_employed dob_years education education_id family_status family_status_id gender income_type debt total_income purpose
0 1 -8437.673028 42 высшее 0 женат / замужем 0 F сотрудник 0 253875.639453 покупка жилья
1 1 -4024.803754 36 среднее 1 женат / замужем 0 F сотрудник 0 112080.014102 приобретение автомобиля
2 0 -5623.422610 33 Среднее 1 женат / замужем 0 M сотрудник 0 145885.952297 покупка жилья
3 3 -4124.747207 32 среднее 1 женат / замужем 0 M сотрудник 0 267628.550329 дополнительное образование
4 0 340266.072047 53 среднее 1 гражданский брак 1 F пенсионер 0 158616.077870 сыграть свадьбу
5 0 -926.185831 27 высшее 0 гражданский брак 1 M компаньон 0 255763.565419 покупка жилья
6 0 -2879.202052 43 высшее 0 женат / замужем 0 F компаньон 0 240525.971920 операции с жильем
7 0 -152.779569 50 СРЕДНЕЕ 1 женат / замужем 0 M сотрудник 0 135823.934197 образование
8 2 -6929.865299 35 ВЫСШЕЕ 0 гражданский брак 1 F сотрудник 0 95856.832424 на проведение свадьбы
9 0 -2188.756445 41 среднее 1 женат / замужем 0 M сотрудник 0 144425.938277 покупка жилья для семьи
10 2 -4171.483647 36 высшее 0 женат / замужем 0 M компаньон 0 113943.491460 покупка недвижимости
11 0 -792.701887 40 среднее 1 женат / замужем 0 F сотрудник 0 77069.234271 покупка коммерческой недвижимости
12 0 NaN 65 среднее 1 гражданский брак 1 M пенсионер 0 NaN сыграть свадьбу
13 0 -1846.641941 54 неоконченное высшее 2 женат / замужем 0 F сотрудник 0 130458.228857 приобретение автомобиля
14 0 -1844.956182 56 высшее 0 гражданский брак 1 F компаньон 1 165127.911772 покупка жилой недвижимости
15 1 -972.364419 26 среднее 1 женат / замужем 0 F сотрудник 0 116820.904450 строительство собственной недвижимости
16 0 -1719.934226 35 среднее 1 женат / замужем 0 F сотрудник 0 289202.704229 недвижимость
17 0 -2369.999720 33 высшее 0 гражданский брак 1 M сотрудник 0 90410.586745 строительство недвижимости
18 0 400281.136913 53 среднее 1 вдовец / вдова 2 F пенсионер 0 56823.777243 на покупку подержанного автомобиля
19 0 -10038.818549 48 СРЕДНЕЕ 1 в разводе 3 F сотрудник 0 242831.107982 на покупку своего автомобиля

Задание 3. Выведите основную информацию о датафрейме с помощью метода info().

In [4]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 21525 entries, 0 to 21524
Data columns (total 12 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   children          21525 non-null  int64  
 1   days_employed     19351 non-null  float64
 2   dob_years         21525 non-null  int64  
 3   education         21525 non-null  object 
 4   education_id      21525 non-null  int64  
 5   family_status     21525 non-null  object 
 6   family_status_id  21525 non-null  int64  
 7   gender            21525 non-null  object 
 8   income_type       21525 non-null  object 
 9   debt              21525 non-null  int64  
 10  total_income      19351 non-null  float64
 11  purpose           21525 non-null  object 
dtypes: float64(2), int64(5), object(5)
memory usage: 2.0+ MB

Задание 4. Выведите количество пропущенных значений для каждого столбца. Используйте комбинацию двух методов.

In [5]:
data.isna().sum()
Out[5]:
children               0
days_employed       2174
dob_years              0
education              0
education_id           0
family_status          0
family_status_id       0
gender                 0
income_type            0
debt                   0
total_income        2174
purpose                0
dtype: int64

Задание 5. В двух столбцах есть пропущенные значения. Один из них — days_employed. Пропуски в этом столбце вы обработаете на следующем этапе. Другой столбец с пропущенными значениями — total_income — хранит данные о доходах. На сумму дохода сильнее всего влияет тип занятости, поэтому заполнить пропуски в этом столбце нужно медианным значением по каждому типу из столбца income_type. Например, у человека с типом занятости сотрудник пропуск в столбце total_income должен быть заполнен медианным доходом среди всех записей с тем же типом.

In [6]:
for i in data['income_type'].unique():
    data.loc[(data['income_type'] == i) & (data['total_income'].isna()), 'total_income'] = \
    data.loc[(data['income_type'] == i), 'total_income'].median()

Задание 6. В данных могут встречаться артефакты (аномалии) — значения, которые не отражают действительность и появились по какой-то ошибке. таким артефактом будет отрицательное количество дней трудового стажа в столбце days_employed. Для реальных данных это нормально. Обработайте значения в этом столбце: замените все отрицательные значения положительными с помощью метода abs().

In [7]:
data['days_employed'] = data['days_employed'].abs()

Задание 7. Для каждого типа занятости выведите медианное значение трудового стажа days_employed в днях.

In [8]:
data.groupby('income_type')['days_employed'].agg('median')
Out[8]:
income_type
безработный        366413.652744
в декрете            3296.759962
госслужащий          2689.368353
компаньон            1547.382223
пенсионер          365213.306266
предприниматель       520.848083
сотрудник            1574.202821
студент               578.751554
Name: days_employed, dtype: float64
У двух типов (безработные и пенсионеры) получатся аномально большие значения. Исправить такие значения сложно, поэтому оставьте их как есть. Тем более этот столбец не понадобится вам для исследования.

Задание 8. Выведите перечень уникальных значений столбца children.

In [9]:
data['children'].unique()
Out[9]:
array([ 1,  0,  3,  2, -1,  4, 20,  5])

Задание 9. В столбце children есть два аномальных значения. Удалите строки, в которых встречаются такие аномальные значения из датафрейма data.

In [10]:
data = data[(data['children'] != -1) & (data['children'] != 20)]

Задание 10. Ещё раз выведите перечень уникальных значений столбца children, чтобы убедиться, что артефакты удалены.

In [11]:
data['children'].unique()
Out[11]:
array([1, 0, 3, 2, 4, 5])

Задание 11. Заполните пропуски в столбце days_employed медианными значениями по каждого типа занятости income_type.

In [12]:
for i in data['income_type'].unique():
    data.loc[(data['income_type'] == i) & (data['days_employed'].isna()), 'days_employed'] = \
    data.loc[(data['income_type'] == i), 'days_employed'].median()

Задание 12. Убедитесь, что все пропуски заполнены. Проверьте себя и ещё раз выведите количество пропущенных значений для каждого столбца с помощью двух методов.

In [13]:
data.isna().sum()
Out[13]:
children            0
days_employed       0
dob_years           0
education           0
education_id        0
family_status       0
family_status_id    0
gender              0
income_type         0
debt                0
total_income        0
purpose             0
dtype: int64

Задание 13. Замените вещественный тип данных в столбце total_income на целочисленный с помощью метода astype().

In [14]:
data['total_income'] = data['total_income'].astype(int)

Задание 14. Обработайте неявные дубликаты в столбце education. В этом столбце есть одни и те же значения, но записанные по-разному: с использованием заглавных и строчных букв. Приведите их к нижнему регистру. Проверьте остальные столбцы.

In [15]:
data['education'] = data['education'].str.lower()

Задание 15. Выведите на экран количество строк-дубликатов в данных. Если такие строки присутствуют, удалите их.

In [16]:
data.duplicated().sum()
Out[16]:
71
In [17]:
data = data.drop_duplicates()

Задание 16. На основании диапазонов, указанных ниже, создайте в датафрейме data столбец total_income_category с категориями:

  • 0–30000 — 'E';
  • 30001–50000 — 'D';
  • 50001–200000 — 'C';
  • 200001–1000000 — 'B';
  • 1000001 и выше — 'A'.

Например, кредитополучателю с доходом 25000 нужно назначить категорию 'E', а клиенту, получающему 235000, — 'B'. Используйте собственную функцию с именем categorize_income() и метод apply().

In [18]:
def categorize_income(income):
    try:
        if 0 <= income <= 50000:
            return 'Зарплата до 50 тыс.₽'
        elif 50001 <= income <= 100000:
            return 'от 50 до 100 тыс. ₽'
        elif 100001 <= income <= 150000:
            return 'от 100 до 150 тыс. ₽'
        elif 150001 <= income <= 200000:
            return 'от 150 до 200 тыс. ₽'
        elif 200001 <= income <= 250000:
            return 'от 200 до 250 тыс. ₽'
        elif 250001 <= income <= 300000:
            return 'от 250 до 300 тыс. ₽'
        elif 300001 <= income <= 350000:
            return 'от 300 до 350 тыс. ₽'
        elif 350001 <= income <= 400000:
            return 'от 350 до 400 тыс. ₽'
        elif 400001 <= income <= 450000:
            return 'от 400 до 450 тыс. ₽'
        elif 450001 <= income <= 500000:
            return 'от 450 до 500 тыс. ₽'
        elif income >= 500001:
            return 'Больше 500 тыс. ₽'
    except:
        pass
In [19]:
data['total_income_category'] = data['total_income'].apply(categorize_income)

Задание 17. Выведите на экран перечень уникальных целей взятия кредита из столбца purpose.

In [20]:
data['purpose'].unique()
Out[20]:
array(['покупка жилья', 'приобретение автомобиля',
       'дополнительное образование', 'сыграть свадьбу',
       'операции с жильем', 'образование', 'на проведение свадьбы',
       'покупка жилья для семьи', 'покупка недвижимости',
       'покупка коммерческой недвижимости', 'покупка жилой недвижимости',
       'строительство собственной недвижимости', 'недвижимость',
       'строительство недвижимости', 'на покупку подержанного автомобиля',
       'на покупку своего автомобиля',
       'операции с коммерческой недвижимостью',
       'строительство жилой недвижимости', 'жилье',
       'операции со своей недвижимостью', 'автомобили',
       'заняться образованием', 'сделка с подержанным автомобилем',
       'получение образования', 'автомобиль', 'свадьба',
       'получение дополнительного образования', 'покупка своего жилья',
       'операции с недвижимостью', 'получение высшего образования',
       'свой автомобиль', 'сделка с автомобилем',
       'профильное образование', 'высшее образование',
       'покупка жилья для сдачи', 'на покупку автомобиля', 'ремонт жилью',
       'заняться высшим образованием'], dtype=object)

Задание 18. Создайте функцию, которая на основании данных из столбца purpose сформирует новый столбец purpose_category, в который войдут следующие категории:

  • 'операции с автомобилем',
  • 'операции с недвижимостью',
  • 'проведение свадьбы',
  • 'получение образования'.

Например, если в столбце purpose находится подстрока 'на покупку автомобиля', то в столбце purpose_category должна появиться строка 'операции с автомобилем'.

Используйте собственную функцию с именем categorize_purpose() и метод apply(). Изучите данные в столбце purpose и определите, какие подстроки помогут вам правильно определить категорию.

In [21]:
def categorize_purpose(row):
    try:
        if 'автом' in row:
            return 'операции с автомобилем'
        elif 'жил' in row or 'недвиж' in row:
            return 'операции с недвижимостью'
        elif 'свад' in row:
            return 'проведение свадьбы'
        elif 'образов' in row:
            return 'получение образования'
    except:
        return 'нет категории'
In [22]:
data['purpose_category'] = data['purpose'].apply(categorize_purpose)

3.1 Есть ли зависимость между количеством детей и возвратом кредита в срок?¶

In [23]:
# смотрим уникальные значения столбца children
data.children.value_counts()
Out[23]:
0    14091
1     4808
2     2052
3      330
4       41
5        9
Name: children, dtype: int64
In [24]:
# Напишем функицю для разбиения количества детей на категории
def children_category(count): 
    
    a = count['children']
    try:
        if a == 0:
            return 'Нет детей'
        elif a == 1:
            return '1 ребенок'
        elif a == 2:
            return '2 ребенка'
        elif a == 3:
            return '3 ребенка'
        elif a == 4:
            return '4 ребенка'
        elif a == 5:
            return '5 детей'
        return 'Многодетные'
    except:
        pass
    
data['children_category'] = data.apply(children_category, axis=1)
# Отсортируем по убыванию и видим предсказуемую закономерность 
data.groupby('children_category', as_index=False)['children'].count().sort_values('children', ascending=False)
Out[24]:
children_category children
5 Нет детей 14091
0 1 ребенок 4808
1 2 ребенка 2052
2 3 ребенка 330
3 4 ребенка 41
4 5 детей 9
In [25]:
# Функция которая нам подсчитает в процентах зависимость между количеством детей и возвратом кредита в срок
def children_analysis(data, index):
    
    # построим сводную таблицу
    data_children_pivot = data.pivot_table(index=index, \
                             values='debt', aggfunc=['sum', 'count', 'mean']).reset_index()
    
    # Переименуем столбцы, чтобы не запутаться.
    data_children_pivot = data_children_pivot.set_axis([index, 'sum_debt', 'count_family', 'correlation'], axis='columns')
    
    # Возвращаем и сортируем по возрастанию
    return data_children_pivot.sort_values('correlation')

☑️ Для начала необходимо понять, какие значения мы будем брать за основу, для проведения качественного анализа. Предлагаю заострить наше внимение на колонке count_family и не брать во внимание значения ниже 1000, дабы не искажать реальную зависимость.

In [26]:
# Добавляем немного стиля и красок и смотрим, что получилось
display(children_analysis(data, 'children_category') \
        .style.format({'correlation': '{:.2%}'}) \
        .background_gradient(cmap='Reds',subset='correlation'))
  children_category sum_debt count_family correlation
4 5 детей 0 9 0.00%
5 Нет детей 1063 14091 7.54%
2 3 ребенка 27 330 8.18%
0 1 ребенок 444 4808 9.23%
1 2 ребенка 194 2052 9.45%
3 4 ребенка 4 41 9.76%
In [27]:
fig = px.line(children_analysis(data, 'children_category'), x='correlation', y='children_category', title='График зависимости между количеством детей и возвратом кредита в срок')
fig.show()

Вывод:

Однозначно, на первый взгляд прогладывается прямая зависимость между количеством детей и возвратом кредита в срок. Так с увеличением количества детей мы видим увеличение количества просроченных задолженностей, хотя люди с 3 детьми чаще платят в срок чем люди с 1 ребенком. Данные не однозначные, возможно, нужна большая выборка, чем та, которую мы имеем. Бездетные, как правило реже просрачивают оплату по кредиту, чем люди с детьми.

3.2 Есть ли зависимость между семейным положением и возвратом кредита в срок?¶

In [28]:
display(children_analysis(data, 'family_status') \
        .style.format({'correlation': '{:.2%}'}) \
        .background_gradient(cmap='Reds',subset='correlation'))
  family_status sum_debt count_family correlation
2 вдовец / вдова 63 951 6.62%
1 в разводе 84 1189 7.06%
4 женат / замужем 927 12261 7.56%
3 гражданский брак 385 4134 9.31%
0 Не женат / не замужем 273 2796 9.76%
In [29]:
plt.figure(figsize=(6,6))
ax = sns.barplot(data = children_analysis(data, 'family_status'), x ='family_status', y = 'correlation')
ax.set_xticklabels(children_analysis(data, 'family_status').family_status, rotation=45)
sns.despine()

Вывод:

В данном вопросе, вывод более менее очевиден. Чем серьезнее отношение - тем серьезные взгляды на жизнь! Если говорить про вдовцов (*я не берусь рассуждать о возрасте овдовевших людей*) и людей находящиеся в разводе, то когнитивное мышление данного контингента людей надодится после эмоциональной встряски - и следовательно - они к обязательствам относятся менее халатнее нежели людей нахоядщийся вне официального брака, то бишь, кредит берется более осознанно - возможно, данная зависимость косвенная, но все возможно

3.3 Есть ли зависимость между уровнем дохода и возвратом кредита в срок?¶

Разделим заемщиков на когорты в зависимости от величины дохода:

  • 0–30000 — 'E';
  • 30001–50000 — 'D';
  • 50001–200000 — 'C';
  • 200001–1000000 — 'B';
  • 1000001 и выше — 'A'.

Мое решение


Я решил заменить предложенную категоризацию по группам [A,B,C,D,E], на более детальное: считаю, что уровень дохода нужно определить конкретным диапозоном, иначе никакого анализа не получится. В данном случае уровень дохода разделен диапозоном в 50 тыс. рублей.
In [30]:
display(children_analysis(data, 'total_income_category').sort_values(by='count_family', ascending=False) \
        .style.format({'correlation': '{:.2%}'}) \
        .background_gradient(cmap='Reds',subset='correlation'))
  total_income_category sum_debt count_family correlation
2 от 100 до 150 тыс. ₽ 619 7110 8.71%
3 от 150 до 200 тыс. ₽ 403 4738 8.51%
10 от 50 до 100 тыс. ₽ 331 4073 8.13%
4 от 200 до 250 тыс. ₽ 162 2242 7.23%
5 от 250 до 300 тыс. ₽ 88 1323 6.65%
6 от 300 до 350 тыс. ₽ 51 617 8.27%
1 Зарплата до 50 тыс.₽ 23 371 6.20%
7 от 350 до 400 тыс. ₽ 24 329 7.29%
0 Больше 500 тыс. ₽ 14 222 6.31%
8 от 400 до 450 тыс. ₽ 13 195 6.67%
9 от 450 до 500 тыс. ₽ 4 111 3.60%

Вывод

Если придерживаться этого правила, то нам, для качественного анализа подходят группы со следующим уровнем дохода:

  • от 50 до 100 тыс. ₽
  • от 100 до 150 тыс. ₽
  • от 150 до 200 тыс. ₽
  • от 200 до 250 тыс. ₽
  • от 250 до 300 тыс. ₽

В целом мы видим, что меньше всего просрочка у самых обеспеченных людей, однозначно, существует корреляция между уровнем дохода и возвратом кредита в срок. В красной зоне ребята с условно средним доходом.

3.4 Как разные цели кредита влияют на его возврат в срок?¶

In [31]:
display(children_analysis(data, 'purpose_category') \
        .style.format({'correlation': '{:.2%}'}) \
        .background_gradient(cmap='Reds',subset='correlation'))
  purpose_category sum_debt count_family correlation
1 операции с недвижимостью 780 10751 7.26%
3 проведение свадьбы 183 2313 7.91%
2 получение образования 369 3988 9.25%
0 операции с автомобилем 400 4279 9.35%

Вывод:

В это таблице, все стоит на своих местах - операции с недвижимость в большинстве из случаев связаны с ипотечным кредитом - следовательно тут ряд гос. льгот в виде материнского капитала и прочих льгот.

Не знал, что на свадьбу берут кредиты - удивительно - но тут также все ясно

Автомобиль и образование в красной зоне - и тут ясно почему, я думаю, что все наслышаны всеми мутными историями и серыми схемами...

3.5 Приведите возможные причины появления пропусков в исходных данных.¶

Ответ:

Думаю, что тут две причины: человеческий и технический фактор

3.6 Объясните, почему заполнить пропуски медианным значением — лучшее решение для количественных переменных.¶

Ответ:

Медиана - это робастная характеристика, которая игнорирует или же сглаживает случайные или не случайные выбросы и катаклизмы в целом. И если данные имеют нормальное распределение и их количество велико, то медиана будет равняться среднему значению.

Общий вывод

В ходе исследования надёжности заемщиков были изучены полученные данные, обработаны и заполнены пропуски, удалены дубликаты и клиенты были объединены в группы по разным параметрам. По итогам проделанной работы можно сделать следующие выводы:

В целом, бездетные семья реже допускают задержки в платежах по кредиту, % должников держится на уровне 7.54%, но данные не однозначные, возможно, нужна большая выборка, чем та, которую мы имеем. У группы клиентов, которые находятся или когда-либо находились в браке % должников на уровне 7% - это ниже на 2.1% ниже, чем у людей никогда не состоявших в официальном браке. Уровень дохода напрямую коррелирует с возвратом кредита в срок - в зоне риска по просрочке платежа люди со средним заработком.

Чаще всего возвращают в срок кредиты на собственное жилье, а больше всего задержек по кредитам на автомобили и образование. У групп клиентов, целью кредита у которых является автомобиль и образование: 9.3% должников (на 1.2% выше среднего).

Резюмируя можно сказать, что разброс значений по просроченным платежам не превышает двух процентов (от 7 до 9%), что означает разницу в вероятности просрочки платежа на 20%.